#include "sockfunc.h"
#include "define.h"
#include "wrapfunc.h"
#include <error.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>


const char* 
sock_ntop(const struct sockaddr *sa, socklen_t len) {
	// IPv4 和 IPv6 地址字符串形式长度参考 INET_ADDRSTRLEN 和 INET6_ADDRSTRLEN
	static char buf[INET6_ADDRSTRLEN + 10];

	if (sa->sa_family == AF_INET) {
        const struct sockaddr_in *sain = (const struct sockaddr_in*)sa;
        char ip[INET_ADDRSTRLEN];
        if (inet_ntop(sain->sin_family, &sain->sin_addr, ip, sizeof(ip)) == NULL)
            return NULL;
        snprintf(buf, sizeof(buf), "%s:%d", ip, ntohs(sain->sin_port));
        return buf;

	} else if (sa->sa_family == AF_INET6) {
        const struct sockaddr_in6 *sain = (const struct sockaddr_in6*)sa;
        char ip[INET6_ADDRSTRLEN];
        if (inet_ntop(sain->sin6_family, &sain->sin6_addr, ip, sizeof(ip)) == NULL)
            return NULL;
        snprintf(buf, sizeof(buf), "[%s]:%d", ip, ntohs(sain->sin6_port));
        return buf;

	} else {
        (void)len;
		return NULL;
	}
}

ssize_t
readn(int fd, void *buf, size_t nbytes) {
    char *ptr = buf;
    size_t left = nbytes;
    ssize_t n = 0;
    while (left > 0) {
        n = read(fd, ptr, left);
        if (n < 0) {
            if (errno == EINTR)
                continue;
            else
                return -1;
        } else if (n == 0) {
            break;
        }

        left -= n;
        ptr += n;
        if (left == 0)
            break;
    }
    return nbytes - left;
}

ssize_t
Readn(int fd, void *buf, size_t nbytes) {
    ssize_t ret;
    if ((ret=readn(fd, buf, nbytes)) < 0)
        error_dump("fail to readn");
    return ret;
}

ssize_t
writen(int fd, const void *buf, size_t nbytes) {
    const char *ptr = buf;
    size_t left = nbytes;
    ssize_t n;
    while (left > 0) {
        n = write(fd, ptr, left);
        if (n < 0) {
            if (errno == EINTR)
                continue;
            else
                return -1;
        }

        left -= n;
        ptr += n;
        if (left == 0)
            break;
    }
    return nbytes;
}

ssize_t
Writen(int fd, const void *buf, size_t nbytes) {
    ssize_t ret;
    if ((ret=writen(fd, buf, nbytes)) < 0)
        error_dump("fail to writen");
    return ret;
}

ssize_t
readline_slow(int fd, void *buf, size_t maxbytes) {
    char *ptr = buf;
    char c;
    size_t i;
    ssize_t n;
    for (i = 0; i < maxbytes;) {
        n = read(fd, &c, 1);
        if (n < 0) {
            if (errno == EINTR) {
                continue;
            } else {
                *ptr = 0;
                return -1;
            }
        } else if (n == 0) {
            *ptr = 0;
            break;
        }

        if (c == '\n') { // exclude \n
            *ptr = 0;
            break;
        } else {
            *ptr++ = i == maxbytes-1 ? 0 : c;
        }
        i++;
    }
    return i;
}


static int
_myread(int fd, char *c) {
    static char buf[4096];
    static int index = 0;
    static ssize_t readbytes = 0;
    
    if (index >= readbytes) {
        for(;;) {
            index = 0;
            readbytes = read(fd, buf, sizeof(buf));
            if (readbytes < 0) {
                if (errno == EINTR)
                    continue;
                else
                    return -1;
            } else if (readbytes == 0) {
                return 0;
            }
            break;
        }
    }

    *c = buf[index++];
    return 1;
}

ssize_t
readline_buffer(int fd, void *buf, size_t maxbytes) {
    char *ptr = buf;
    char c = 0;
    int n;
    size_t i;
    for (i = 0; i < maxbytes;) {
        n = _myread(fd, &c);
        if (n <= 0) {
            *ptr = 0;
            if (n == 0) 
                break;
            else
                return -1;
        }

        if (c == '\n') { // exclude \n
            *ptr = 0;
            break;
        } else {
            *ptr++ = i == maxbytes-1 ? 0 : c;
        }
        i++;
    }

    return i;
}

ssize_t
Readline_buffer(int fd, void *buf, size_t maxbytes) {
    ssize_t n;
    if ((n=readline_buffer(fd, buf, maxbytes)) < 0)
        error_dump("fail to readline_buffer");
    return n;
}

int 
sockfd_to_family(int fd) {
    struct sockaddr_storage ss;
    socklen_t len = sizeof(ss);
    if (getsockname(fd, (struct sockaddr*)&ss, &len))
        return -1;
    return ss.ss_family;
}

struct addrinfo*
host_serv(const char *host, const char *service, int family, int socktype) {
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = socktype;
    struct addrinfo *res;
    if (getaddrinfo(host, service, &hints, &res) != 0)
        return NULL;
    return res;
}

int
tcp_connect(const char *host, const char *service) {
    int n;
    struct addrinfo *res = NULL;
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if ((n=getaddrinfo(host, service, &hints, &res)) != 0)
        error_quit("fail to getaddrinfo, error:%s", gai_strerror(n));

    int sockfd;
    struct addrinfo *ptr = res;
    do {
        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sockfd < 0)
            continue;
        error_msg("try to connect:%s", sock_ntop(res->ai_addr, res->ai_addrlen));
        if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
            break;
        Close(sockfd);
    } while ((res=res->ai_next) != NULL);

    if (res == NULL)
        error_quit("fail to connect host service");
    freeaddrinfo(ptr);

    return sockfd;
}

int
tcp_listen(const char *host, const char *service, int *addrlenp) {
    int n;
    struct addrinfo *res = NULL;
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if ((n=getaddrinfo(host, service, &hints, &res)) != 0)
        error_quit("fail to getaddrinfo, error:%s", gai_strerror(n));

    int listenfd;
    struct addrinfo *ptr = res;
    do {
        listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (listenfd < 0)
            continue;
        int reuse = 1;
        Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
        if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
            break;
        Close(listenfd);
    } while ((res=res->ai_next) != NULL);

    if (res == NULL)
        error_quit("fail to bind host service");

    error_msg("bind to %s", sock_ntop(res->ai_addr, res->ai_addrlen));
    Listen(listenfd, LISTENQ);
    if (addrlenp)
        *addrlenp = res->ai_addrlen;
    freeaddrinfo(ptr);

    return listenfd;   
}

int
udp_client(const char *host, const char *service, struct sockaddr **saptr, socklen_t *lenptr) {
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    struct addrinfo *res;
    int n;
    if ((n=getaddrinfo(host, service, &hints, &res)) != 0)
        error_quit("fail to getaddrinfo, error:%s", gai_strerror(n));

    int sockfd;
    struct addrinfo *ptr = res;
    do {
        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sockfd >= 0)
            break;
    } while ((res=res->ai_next) != NULL);

    if (res == NULL)
        error_quit("fail to create udp client");

    *saptr = malloc(res->ai_addrlen);
    memcpy(*saptr, res->ai_addr, res->ai_addrlen);
    *lenptr = res->ai_addrlen;
    freeaddrinfo(ptr);

    return sockfd;
}

int
udp_connect(const char *host, const char *service) {
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    struct addrinfo *res;
    int n;
    if ((n=getaddrinfo(host, service, &hints, &res)) != 0)
        error_quit("fail to getaddrinfo, error:%s", gai_strerror(n));

    int sockfd;
    struct addrinfo *ptr = res;
    do {
        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sockfd < 0)
            continue;
        if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
            break;
        Close(sockfd);
    } while ((res=res->ai_next) != NULL);

    if (res == NULL)
        error_quit("fail to create udp connect");
    freeaddrinfo(ptr);

    return sockfd;
}

int
udp_server(const char *host, const char *service, socklen_t *lenptr) {
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    struct addrinfo *res;
    int n;
    if ((n=getaddrinfo(host, service, &hints, &res)) != 0)
        error_quit("fail to getaddrinfo, error:%s", gai_strerror(n));

    int sockfd;
    struct addrinfo *ptr = res;
    do {
        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sockfd < 0)
            continue;
        if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0)
            break;
        Close(sockfd);
    } while ((res=res->ai_next) != NULL);

    if (res == NULL)
        error_quit("fail to create udp server");
    error_msg("udp bind to %s", sock_ntop(res->ai_addr, res->ai_addrlen));
    if (lenptr)
        *lenptr = res->ai_addrlen;
    freeaddrinfo(ptr);
    
    return sockfd;
}
